/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.core.gameloop;

import hextd.HexTD;
import hextd.HexTD.GameResult;
import hextd.HexTD.GameState;
import hextd.blackBox.BlackBox;
import hextd.core.Context;
import hextd.core.Context.GameTime;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author hylke
 */
public interface GameLoopImplementation {

	public long execute();

	/**
	 * actual implementations
	 */
	public class GliNull implements GameLoopImplementation {

		@Override
		public long execute() {
			throw new UnsupportedOperationException("Not supported yet.");
		}
	}

	public class GliSpPlaying implements GameLoopImplementation {

		private Context context;
		private final Context.GameTime gameTime;
		private int graceTicks = 0;

		public GliSpPlaying(Context context, GameTime gameTime) {
			this.context = context;
			this.gameTime = gameTime;
		}

		@Override
		public long execute() {
			int curGameTime;

			GameState gameState = context.getGameState();
			assert gameState == GameState.spPlaying;
			GameResult gameResult = context.getGameResult();
			assert gameResult == GameResult.active;

			synchronized (gameTime) {
				if (context.isWaveActive() || graceTicks-- > 0) {
					curGameTime = gameTime.increment();
					context.doTick(curGameTime);
				}
			}
			if (HexTD.debug) {
				context.setDebug(" " + gameTime);
			}

			return 0;
		}

		public void setGraceTicks(int graceTicks) {
			this.graceTicks = graceTicks;
		}
	}

	public class GliSpPlayingWaveless implements GameLoopImplementation {

		private Context context;
		private final Context.GameTime gameTime;

		public GliSpPlayingWaveless(Context context, GameTime gameTime) {
			this.context = context;
			this.gameTime = gameTime;
		}

		@Override
		public long execute() {
			int curGameTime = 0;

			GameState gameState = context.getGameState();
			assert gameState == GameState.spPlayingWaveless;
			GameResult gameResult = context.getGameResult();
			assert gameResult == GameResult.active;

			synchronized (gameTime) {
				curGameTime = gameTime.increment();
				context.doTick(curGameTime);
			}
			if (HexTD.debug) {
				context.setDebug(" " + gameTime);
			}

			return 0;
		}
	}

	public class GliReplaying implements GameLoopImplementation {

		private Context context;
		private final Context.GameTime gameTime;
		private BlackBox blackBoxPlayback;
		private int graceTicks = 0;

		public GliReplaying(Context context, GameTime gameTime, BlackBox blackBoxPlayback) {
			this.context = context;
			this.gameTime = gameTime;
			this.blackBoxPlayback = blackBoxPlayback;
		}

		@Override
		public long execute() {
			int curGameTime = 0;

			GameState gameState = context.getGameState();
			assert gameState == GameState.replaying;
			GameResult gameResult = context.getGameResult();
			assert gameResult == GameResult.active;

			synchronized (gameTime) {
				blackBoxPlayback.doActions();

				curGameTime = gameTime.getTime();

				if (context.isWaveActive() || graceTicks-- > 0) {
					curGameTime = gameTime.increment();
					context.doTick(curGameTime);
				} else if (graceTicks < -1) {
					System.out.println("GameLoop::Run: " + curGameTime + " Sync error: Gracetime ran out. Wave started anyway!");
					if (!context.tryNextWave()) {
						System.out.println("GameLoop::Run: But there are no waves, exiting!");
						gameResult = GameResult.ended;
						return 0;
					}
				}
			}
			if (HexTD.debug) {
				context.setDebug("" + gameTime);
			}

			return 0;
		}

		public void setGraceTicks(int graceTicks) {
			this.graceTicks = graceTicks;
		}
	}

	public class GliChecking implements GameLoopImplementation {

		private Context context;
		private final Context.GameTime gameTime;
		private BlackBox blackBoxPlayback;
		private int graceTicks = 0;

		public GliChecking(Context context, GameTime gameTime, BlackBox blackBoxPlayback) {
			this.context = context;
			this.gameTime = gameTime;
			this.blackBoxPlayback = blackBoxPlayback;
		}

		@Override
		public long execute() {
			int curGameTime = 0;

			GameState gameState = context.getGameState();
			assert gameState == GameState.checking;
			GameResult gameResult = context.getGameResult();
			assert gameResult == GameResult.active;

			synchronized (gameTime) {
				blackBoxPlayback.doActions();

				if (context.isWaveActive() || graceTicks-- > 0 || context.getWaveCount() == 0) {
					curGameTime = gameTime.increment();
					context.doTick(curGameTime);
				} else if (graceTicks < -1) {
					curGameTime = gameTime.getTime();
					System.out.println("gameLoopChecking::Run: " + curGameTime + " Sync error: Gracetime ran out. Wave started anyway!");
					if (!context.tryNextWave()) {
						System.out.println("gameLoopChecking::Run: But there are no waves, exiting!");
						gameResult = GameResult.ended;
						return 0;
					}
				}
			}
			if (HexTD.debug) {
				context.setDebug("" + gameTime);
			}

			return 0;
		}

		public void setGraceTicks(int graceTicks) {
			this.graceTicks = graceTicks;
		}
	}

	public class GliMpPlaying implements GameLoopImplementation {

		private Context context;
		private final Context.GameTime gameTime;
		private BlackBox blackBoxRecord;

		public GliMpPlaying(Context context, GameTime gameTime, BlackBox blackBoxRecord) {
			this.context = context;
			this.gameTime = gameTime;
			this.blackBoxRecord = blackBoxRecord;
		}

		@Override
		public long execute() {
			int curGameTime = 0, lowestTick, deltaTime = 0, backlogSize = 0;
			boolean backlogOk;
			boolean waiting = false;

			GameState gameState = context.getGameState();
			assert gameState == GameState.mpPlaying;
			GameResult gameResult = context.getGameResult();
			assert gameResult == GameResult.active;

			synchronized (gameTime) {
				curGameTime = gameTime.getTime();
				lowestTick = context.getLowestTick();
				deltaTime = curGameTime - lowestTick;

				backlogOk = blackBoxRecord.checkBackLog();
				backlogSize = blackBoxRecord.getBacklogSize();
				if (!backlogOk) {
					Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, "Backlog NOT OK!");
				}

				if (curGameTime > lowestTick + Context.actionDelay - 2) {
					if (!waiting) {
						waiting = true;
						context.setStatus("Waiting for " + context.getSlowestPlayer().getPlayerName() + ".");
					}
				} else {
					curGameTime = gameTime.increment();
					// Run a gametick
					context.doTick(curGameTime);

					if (waiting) {
						waiting = false;
						context.setStatus(null);
					}
				}
			}
			if (HexTD.debug) {
				context.setDebug("" + deltaTime + " " + gameTime);
			}

			long sleepTime = 0;
			if (deltaTime > Context.actionDelay - 15) {
				sleepTime += deltaTime - (Context.actionDelay - 15);
			}
			if (deltaTime < 0) {
				sleepTime += deltaTime;
			}
			return sleepTime;
		}
	}

	public class GliMpChecking implements GameLoopImplementation {

		private Context context;
		private final Context.GameTime gameTime;
		private BlackBox blackBoxPlayback;

		public GliMpChecking(Context context, GameTime gameTime, BlackBox blackBoxPlayback) {
			this.context = context;
			this.gameTime = gameTime;
			this.blackBoxPlayback = blackBoxPlayback;
		}

		@Override
		public long execute() {
			int curGameTime = 0;

			GameState gameState = context.getGameState();
			assert gameState == GameState.mpChecking;
			GameResult gameResult = context.getGameResult();
			assert gameResult == GameResult.active;

			synchronized (gameTime) {
				blackBoxPlayback.doActions();

				curGameTime = gameTime.increment();
				context.doTick(curGameTime);

			}
			if (HexTD.debug) {
				context.setDebug("" + gameTime);
			}
			return 0;
		}
	}

	public class GliMpCatchup implements GameLoopImplementation {

		private Context context;
		private final Context.GameTime gameTime;
		private BlackBox blackBoxRecord;
		private int targetTick = 0;

		public GliMpCatchup(Context context, GameTime gameTime, BlackBox blackBoxRecord) {
			this.context = context;
			this.gameTime = gameTime;
			this.blackBoxRecord = blackBoxRecord;
		}

		@Override
		public long execute() {
			int curGameTime = 0, lowestTick, deltaTime = 0, backlogSize = 0;
			boolean backlogOk;
			boolean waiting = false;

			GameState gameState = context.getGameState();
			assert gameState == GameState.mpCatchup;
			GameResult gameResult = context.getGameResult();
			assert gameResult == GameResult.active;

			synchronized (gameTime) {
				curGameTime = gameTime.getTime();
				lowestTick = context.getLowestTick();
				deltaTime = curGameTime - lowestTick;

				backlogOk = blackBoxRecord.checkBackLog();
				backlogSize = blackBoxRecord.getBacklogSize();
				if (!backlogOk) {
					Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, "Backlog NOT OK!");
				}

				if (curGameTime > lowestTick + Context.actionDelay - 2) {
					if (!waiting) {
						waiting = true;
						context.setStatus("Waiting for " + context.getSlowestPlayer().getPlayerName() + ".");
					}
				} else {
					curGameTime = gameTime.increment();
					// Run a gametick
					context.doTick(curGameTime);
					if (waiting) {
						waiting = false;
						context.setStatus(null);
					}
				}
			}
			if (HexTD.debug) {
				context.setDebug("" + deltaTime + " " + gameTime);
			}

			if (curGameTime >= targetTick + 1) {
				context.setGameState(GameState.mpPlaying);
			}

			return 0;
		}

		/**
		 * @param set the target tick, how far the game already progressed and
		 * how much we have to catch up to.
		 */
		public void setTargetTick(int targetTick) {
			this.targetTick = targetTick;
		}
	}

	public class GliMpWatching implements GameLoopImplementation {

		private Context context;
		private final Context.GameTime gameTime;
		private BlackBox blackBoxRecord;

		public GliMpWatching(Context context, GameTime gameTime, BlackBox blackBoxRecord) {
			this.context = context;
			this.gameTime = gameTime;
			this.blackBoxRecord = blackBoxRecord;
		}

		@Override
		public long execute() {
			int curGameTime = 0, lowestTick, deltaTime = 0;
			boolean backlogOk;
			boolean waiting = false;

			GameState gameState = context.getGameState();
			assert gameState == GameState.mpWatching;
			GameResult gameResult = context.getGameResult();
			assert gameResult == GameResult.active;

			synchronized (gameTime) {
				curGameTime = gameTime.getTime();
				lowestTick = context.getLowestTick();
				deltaTime = curGameTime - lowestTick;

				backlogOk = blackBoxRecord.checkBackLog();
				if (!backlogOk) {
					Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, "Backlog NOT OK!");
				}

				if (curGameTime > lowestTick + Context.actionDelay - 2) {
					if (!waiting) {
						waiting = true;
						context.setStatus("Waiting for " + context.getSlowestPlayer().getPlayerName() + ".");
					}
				} else {
					curGameTime = gameTime.increment();
					context.doTick(curGameTime);
					if (waiting) {
						waiting = false;
						context.setStatus(null);
					}
				}
			}
			if (HexTD.debug) {
				context.setDebug("" + deltaTime + " " + gameTime);
			}

			long sleepTime = 0;
			if (deltaTime > Context.actionDelay - 15) {
				sleepTime += deltaTime - (Context.actionDelay - 15);
			}
			if (deltaTime < 0) {
				sleepTime += deltaTime;
			}
			return sleepTime;
		}
	}
}
